package com.bumptech.glide.load.resource.bitmap;

import com.bumptech.glide.load.Transformation;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.util.LogUtil;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Synthetic;
import ohos.agp.graphics.TextureHolder;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.media.image.PixelMap;
import ohos.media.image.common.AlphaType;
import ohos.media.image.common.PixelFormat;
import ohos.agp.utils.RectFloat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/** A class with methods to efficiently resize Bitmaps. */
// Legacy Public APIs.
@SuppressWarnings("WeakerAccess")
public final class TransformationUtils {
  private static final String TAG = "TransformationUtils";
  private static Paint DEFAULT_PAINT = new Paint();
  private static final Paint CIRCLE_CROP_SHAPE_PAINT = new Paint();
  private static final Paint CIRCLE_CROP_BITMAP_PAINT = new Paint();

  static {
      try{
          DEFAULT_PAINT.setDither(true);
          DEFAULT_PAINT.setFilterBitmap(true);

          CIRCLE_CROP_SHAPE_PAINT.setDither(true);
          CIRCLE_CROP_SHAPE_PAINT.setFilterBitmap(true);
          CIRCLE_CROP_SHAPE_PAINT.setAntiAlias(true);

          CIRCLE_CROP_BITMAP_PAINT.setDither(true);
          CIRCLE_CROP_BITMAP_PAINT.setFilterBitmap(true);
          CIRCLE_CROP_BITMAP_PAINT.setAntiAlias(true);
          CIRCLE_CROP_BITMAP_PAINT.setBlendMode(BlendMode.SRC_IN);

      }
      catch(RuntimeException e){}
  }

  // See #738.
  private static final Set<String> MODELS_REQUIRING_BITMAP_LOCK =
      new HashSet<>(
          Arrays.asList(
              // Moto X gen 2
              "XT1085",
              "XT1092",
              "XT1093",
              "XT1094",
              "XT1095",
              "XT1096",
              "XT1097",
              "XT1098",
              // Moto G gen 1
              "XT1031",
              "XT1028",
              "XT937C",
              "XT1032",
              "XT1008",
              "XT1033",
              "XT1035",
              "XT1034",
              "XT939G",
              "XT1039",
              "XT1040",
              "XT1042",
              "XT1045",
              // Moto G gen 2
              "XT1063",
              "XT1064",
              "XT1068",
              "XT1069",
              "XT1072",
              "XT1077",
              "XT1078",
              "XT1079"));

  /**
   * https://github.com/bumptech/glide/issues/738 On some devices, bitmap drawing is not thread
   * safe. This lock only locks for these specific devices. For other types of devices the lock is
   * always available and therefore does not impact performance
   */
  private static final Lock BITMAP_DRAWABLE_LOCK = new NoLock();



  private TransformationUtils() {
    // Utility class.
  }

  public static Lock getBitmapDrawableLock() {
    return BITMAP_DRAWABLE_LOCK;
  }
  private static void clear(Canvas canvas) {
        canvas.setTexture(null);
    }

    private static PixelFormat getNotNullConfig( PixelMap pixelMap) {
        return pixelMap.getImageInfo() != null ? pixelMap.getImageInfo().pixelFormat : PixelFormat.ARGB_8888;
    }

    private static void applyMatrix(
            PixelMap inBitmap, PixelMap targetBitmap, Matrix matrix) {

        BITMAP_DRAWABLE_LOCK.lock();
        try {
            Canvas canvas = new Canvas( new Texture(targetBitmap));
            //canvas.concat(matrix);
            LogUtil.info(TAG, " applyMatrix() and targetBitmap width is " + targetBitmap.getImageInfo().size.width + " targetBitmap height is "+ targetBitmap.getImageInfo().size.height);
            LogUtil.info(TAG, " applyMatrix() and inBitmap width is " + inBitmap.getImageInfo().size.width + " targetBitmap height is "+ inBitmap.getImageInfo().size.height);
            canvas.setMatrix( matrix);
            canvas.drawPixelMapHolder( new PixelMapHolder(inBitmap), 0, 0, DEFAULT_PAINT);
            clear(canvas);
        } finally {
            BITMAP_DRAWABLE_LOCK.unlock();
        }
    }


  /**
   * A potentially expensive operation to crop the given Bitmap so that it fills the given
   * dimensions. This operation is significantly less expensive in terms of memory if a mutable
   * Bitmap with the given dimensions is passed in as well.
   *
   * @param pool The BitmapPool to obtain a bitmap from.
   * @param inBitmap The Bitmap to resize.
   * @param width The width in pixels of the final Bitmap.
   * @param height The height in pixels of the final Bitmap.
   * @return The resized Bitmap (will be recycled if recycled is not null).
   */
  public static PixelMap centerCrop(
          @NotNull  BitmapPool pool, @NotNull PixelMap inBitmap, int width, int height) {

        if (inBitmap.getImageInfo().size.width == width && inBitmap.getImageInfo().size.height == height) {

            return inBitmap;
        }
        // From ImageView/Bitmap.createScaledBitmap.
        final float scale;
        final float dx;
        final float dy;
        Matrix m = new Matrix();
        if (inBitmap.getImageInfo().size.width * height > width * inBitmap.getImageInfo().size.height) {
            scale = (float) height / (float) inBitmap.getImageInfo().size.height;
            dx = (width - inBitmap.getImageInfo().size.width * scale) * 0.5f;
            dy = 0;
        } else {
            scale = (float) width / (float) inBitmap.getImageInfo().size.width;
            dx = 0;
            dy = (height - inBitmap.getImageInfo().size.height * scale) * 0.5f;
        }

        m.setScale(scale, scale);
        m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));

        PixelMap result = pool.get(width, height, getNotNullConfig(inBitmap));
        // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
        TransformationUtils.setAlpha(inBitmap, result);
        applyMatrix(inBitmap, result, m);

        return result;
    }


    /**
     * Sets the alpha of the Bitmap we're going to re-use to the alpha of the Bitmap we're going to
     * transform. This keeps Bitmap#hasAlpha() consistent before and after
     * the transformation for transformations that don't add or remove transparent pixels.
     *
     * @param inBitmap The Bitmap that will be transformed.
     * @param outBitmap The Bitmap that will be returned from the
     *     transformation.
     */
    public static void setAlpha(PixelMap inBitmap, PixelMap outBitmap) {
        outBitmap.setAlphaType(inBitmap.getImageInfo().alphaType);
    }
 /**
   * An expensive operation to resize the given Bitmap down so that it fits within the given
   * dimensions maintain the original proportions.
   *
   * @param pool The PixelmapPool obtain a bitmap from.
   * @param inBitmap The Bitmap to shrink.
   * @param width The width in pixels the final image will fit within.
   * @param height The height in pixels the final image will fit within.
   * @return A new Bitmap shrunk to fit within the given dimensions, or toFit if toFit's width or
   *     height matches the given dimensions and toFit fits within the given dimensions
   */
  public static PixelMap fitCenter(
      @NotNull BitmapPool pool, @NotNull PixelMap inBitmap, int width, int height) {
      if (inBitmap.getImageInfo().size.width == width && inBitmap.getImageInfo().size.height == height) {
          LogUtil.info(TAG, "requested target size matches input, returning input");

          return inBitmap;
      }
      final float widthPercentage = width / (float) inBitmap.getImageInfo().size.width;
      final float heightPercentage = height / (float) inBitmap.getImageInfo().size.height;
      final float minPercentage = Math.min(widthPercentage, heightPercentage);

      // Round here in case we've decoded exactly the image we want, but take the floor below to
      // avoid a line of garbage or blank pixels in images.
      int targetWidth = Math.round(minPercentage * inBitmap.getImageInfo().size.width);
      int targetHeight = Math.round(minPercentage * inBitmap.getImageInfo().size.height);

      if (inBitmap.getImageInfo().size.width == targetWidth && inBitmap.getImageInfo().size.height == targetHeight) {
          LogUtil.info(TAG, "adjusted target size matches input, returning input");

          return inBitmap;
      }

      // Take the floor of the target width/height, not round. If the matrix
      // passed into drawBitmap rounds differently, we want to slightly
      // overdraw, not underdraw, to avoid artifacts from bitmap reuse.
      targetWidth = (int) (minPercentage * inBitmap.getImageInfo().size.width);
      targetHeight = (int) (minPercentage * inBitmap.getImageInfo().size.height);


     // PixelMap toReuse = pool.get(targetWidth, targetHeight, getNotNullConfig(inBitmap)); TODO
      PixelMap toReuse = createPixelmap(targetWidth,targetHeight,getNotNullConfig(inBitmap));

      // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
      TransformationUtils.setAlpha(inBitmap, toReuse);

      Matrix matrix = new Matrix();
      matrix.setScale(minPercentage, minPercentage);
      applyMatrix(inBitmap, toReuse, matrix);

      return toReuse;
  }


  /**
   * If the Bitmap is smaller or equal to the Target it returns the original size, if not then
   * {@link #fitCenter(PixelmapPool, PixelMap, int, int)} is called instead.
   *
   * @param pool The BitmapPool obtain a bitmap from.
   * @param inBitmap The Bitmap to center.
   * @param width The width in pixels of the target.
   * @param height The height in pixels of the target.
   * @return returns input Bitmap if smaller or equal to target, or toFit if the Bitmap's width or
   *     height is larger than the given dimensions
   */
  public static PixelMap centerInside(
       @NotNull BitmapPool pool, @NotNull PixelMap inBitmap, int width, int height) {
      if (inBitmap.getImageInfo().size.width <= width && inBitmap.getImageInfo().size.height <= height) {

          return inBitmap;
      } else {

          return fitCenter(pool, inBitmap, width, height);
      }
  }  


  /**
   * This is an expensive operation that copies the image in place with the pixels rotated. If
   * possible rather use getOrientationMatrix, and put that as the imageMatrix on an ImageView.
   *
   * @param imageToOrient Image Bitmap to orient.
   * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is
   *     returned unmodified.
   * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap.
   */
  public static PixelMap rotateImage( PixelMap imageToOrient, int degreesToRotate) {

      PixelMap result = imageToOrient;
      try {
          if (degreesToRotate != 0) {
              Matrix matrix = new Matrix();
              matrix.setRotate(degreesToRotate);
              matrix.setRotate(degreesToRotate, (float) imageToOrient.getImageInfo().size.width / 2,
                      (float) imageToOrient.getImageInfo().size.height / 2);

              float outputX, outputY;
              if (degreesToRotate == 90) {
                  outputX = imageToOrient.getImageInfo().size.height;
                  outputY = 0;
              } else {
                  outputX = imageToOrient.getImageInfo().size.height;
                  outputY = imageToOrient.getImageInfo().size.width;
              }
              float[] values = new float[9];
              values = matrix.getData();
              float x1 = values[2];
              float y1 = values[5];
              matrix.postTranslate(outputX - x1, outputY - y1);

              PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
              initializationOptions.size = imageToOrient.getImageInfo().size;
              initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
              result = PixelMap.create(initializationOptions);

              Canvas canvas =new Canvas(new Texture(result));
              canvas.setMatrix(matrix);
              canvas.drawPixelMapHolder( new PixelMapHolder(imageToOrient), 0, 0, DEFAULT_PAINT);
          }
      } catch (Exception e) {

          LogUtil.error(TAG, "Exception when trying to orient image" + e);
      }

      return result;
  }

  /**
   * Get the # of degrees an image must be rotated to match the given exif orientation.
   *
   * @param exifOrientation The exif orientation [1-8]
   * @return the number of degrees to rotate
   */
  public static int getExifOrientationDegrees(int exifOrientation) {
      //TODO:
    return 0;
  }

   /**
   * Rotate and/or flip the image to match the given exif orientation.
   *
   * @param pool A pool that may or may not contain an image of the necessary dimensions.
   * @param inBitmap The bitmap to rotate/flip.
   * @param exifOrientation the exif orientation [1-8].
   * @return The rotated and/or flipped image or toOrient if no rotation or flip was necessary.
   */
  public static PixelMap rotateImageExif(
         @NotNull  BitmapPool pool, @NotNull PixelMap inBitmap, int exifOrientation) {
    return inBitmap;
  }
  /**
   * Returns {@code true} if the given exif orientation indicates that a transformation is necessary
   * and {@code false} otherwise.
   * @param exifOrientation
   * @return boolean
   */
  public static boolean isExifOrientationRequired(int exifOrientation) {
        return false;
  }

    /** Convenience function for drawing a rounded bitmap. */
    private interface DrawRoundedCornerFn {

        void drawRoundedCorners(Canvas canvas, Paint paint, RectFloat rect);
    }

     private static PixelMap getAlphaSafeBitmap(
            BitmapPool pool, PixelMap maybeAlphaSafe) {
        PixelFormat safeConfig = getAlphaSafeConfig(maybeAlphaSafe);

        if (safeConfig.equals(maybeAlphaSafe.getImageInfo().pixelFormat)) {
            return maybeAlphaSafe;
        }

        PixelMap argbBitmap = pool.get(maybeAlphaSafe.getImageInfo().size.width, maybeAlphaSafe.getImageInfo().size.height, safeConfig);
        new Canvas(new Texture(argbBitmap)).drawPixelMapHolder( new PixelMapHolder(maybeAlphaSafe), 0 /*left*/, 0 /*top*/, null /*paint*/);

        // We now own this Pixelmap. It's our responsibility to replace it in the pool outside this method
        // when we're finished with it.
        return argbBitmap;
    }


    private static PixelFormat getAlphaSafeConfig(PixelMap inBitmap) {
        //TODO: There is no support for RGBA_F16 in openharmony

        return PixelFormat.ARGB_8888;
    }

    private PixelMap getRotatedBitmap(PixelMap pixelMap) {
        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.size.width = pixelMap.getImageInfo().size.width;
        initializationOptions.size.height = pixelMap.getImageInfo().size.height;
        return PixelMap.create(pixelMap, initializationOptions);
    }

    private static PixelMap createPixelmap(int width, int height,  PixelFormat config) {
        PixelMap.InitializationOptions opts = new PixelMap.InitializationOptions();
        opts.size = new Size(width, height);
        opts.pixelFormat = config;

        return PixelMap.create( opts);
    }

 /**
   * Crop the image to a circle and resize to the specified width/height. The circle crop will have
   * the same width and height equal to the min-edge of the result image.
   *
   * @param pool The PixelmapPool obtain a bitmap from.
   * @param inBitmap The Bitmap to resize.
   * @param destWidth The width in pixels of the final Bitmap.
   * @param destHeight The height in pixels of the final Bitmap.
   * @return The resized Bitmap (will be recycled if recycled is not null).
   */
  public static PixelMap circleCrop(
      @NotNull BitmapPool pool, @NotNull PixelMap inBitmap, int destWidth, int destHeight) {

        int destMinEdge = Math.min(destWidth, destHeight);
        float radius = destMinEdge / 2f;

        int srcWidth = inBitmap.getImageInfo().size.width;
        int srcHeight = inBitmap.getImageInfo().size.height;

        float scaleX = destMinEdge / (float) srcWidth;
        float scaleY = destMinEdge / (float) srcHeight;
        float maxScale = Math.max(scaleX, scaleY);

        float scaledWidth = maxScale * srcWidth;
        float scaledHeight = maxScale * srcHeight;
        float left = (destMinEdge - scaledWidth) / 2f;
        float top = (destMinEdge - scaledHeight) / 2f;

        RectFloat destRect = new RectFloat(left, top, left + scaledWidth, top + scaledHeight);

        // Alpha is required for this transformation.
        PixelMap toTransform = getAlphaSafeBitmap(pool, inBitmap);

        PixelFormat outConfig = getAlphaSafeConfig(inBitmap);
        //PixelMap result = pool.get(destMinEdge, destMinEdge, outConfig); //TODO:Bugfix
        PixelMap result = createPixelmap(destMinEdge, destMinEdge, outConfig);
        result.setAlphaType(AlphaType.PREMUL);

        BITMAP_DRAWABLE_LOCK.lock();
        try {
            Canvas canvas = new Canvas(new Texture(result));

            // Draw a circle
            canvas.drawCircle(radius, radius, radius, CIRCLE_CROP_SHAPE_PAINT);

            // Draw the bitmap in the circle
            RectFloat srcRect = new RectFloat(0, 0, inBitmap.getImageInfo().size.width, inBitmap.getImageInfo().size.height);
            canvas.drawPixelMapHolderRect(new PixelMapHolder(toTransform), srcRect, destRect, CIRCLE_CROP_BITMAP_PAINT);

            clear(canvas);
        } finally {
            BITMAP_DRAWABLE_LOCK.unlock();
        }

        if (!toTransform.equals(inBitmap)) {
            pool.put(toTransform);
        }

        return result;
    }

  
   /**
   * Creates a bitmap from a source bitmap and rounds the corners.
   *
   * @param inBitmap the source bitmap to use as a basis for the created bitmap.
   * @param width the width of the generated bitmap.
   * @param height the height of the generated bitmap.
   * @param roundingRadius the corner radius to be applied (in device-specific pixels)
    * @param pool
   * @return a {@link Bitmap} similar to inBitmap but with rounded corners.
   * @throws IllegalArgumentException if roundingRadius, width or height is 0 or less.
   * @deprecated Width and height are unused and ignored. Use {@link #roundedCorners(BitmapPool,
   *     Bitmap, int)} instead.
   */
  @Deprecated
  public static PixelMap roundedCorners(
      @NotNull BitmapPool pool,
      @NotNull PixelMap inBitmap,
      @SuppressWarnings("unused") int width,
      @SuppressWarnings("unused") int height,
      int roundingRadius) {
    return roundedCorners(pool, inBitmap, roundingRadius);
  }


/**
   * Creates a bitmap from a source bitmap and rounds the corners.
   *
   * <p>This method does <em>NOT</em> resize the given {@link PixelMap}, it only rounds it's corners.
   * To both resize and round the corners of an image, consider {@link
   * com.bumptech.glide.request.RequestOptions#transform(Transformation[])} and/or {@link
   * com.bumptech.glide.load.MultiTransformation}.
   *
   * @param inBitmap the source bitmap to use as a basis for the created bitmap.
   * @param roundingRadius the corner radius to be applied (in device-specific pixels).
   * @param pool
   * @return a {@link Bitmap} similar to inBitmap but with rounded corners.
   * @throws IllegalArgumentException if roundingRadius, width or height is 0 or less.
   */
  public static PixelMap roundedCorners(
     @NotNull  BitmapPool pool, @NotNull PixelMap inBitmap, final int roundingRadius) {
    Preconditions.checkArgument(roundingRadius > 0, "roundingRadius must be greater than 0.");

      return roundedCorners(
              pool,
              inBitmap,
              new DrawRoundedCornerFn() {
                  @Override
                  public void drawRoundedCorners(Canvas canvas, Paint paint, RectFloat rect) {
                      canvas.drawRoundRect(rect, roundingRadius, roundingRadius, paint);
                  }
              });
  }

 /**
   * Creates a bitmap from a source bitmap and rounds the corners, applying a potentially different
   * [X, Y] radius to each corner.
   *
   * <p>This method does <em>NOT</em> resize the given {@link Bitmap}, it only rounds it's corners.
   * To both resize and round the corners of an image, consider {@link
   * com.bumptech.glide.request.RequestOptions#transform(Transformation[])} and/or {@link
   * com.bumptech.glide.load.MultiTransformation}.
   *
   * @param inBitmap the source bitmap to use as a basis for the created bitmap.
   * @param topLeft top-left radius
   * @param topRight top-right radius
   * @param bottomRight bottom-right radius
   * @param bottomLeft bottom-left radius
   * @param pool
   * @return a {@link Bitmap} similar to inBitmap but with rounded corners.
   */
  public static PixelMap roundedCorners(
      @NotNull BitmapPool pool,
       @NotNull PixelMap inBitmap,
      final float topLeft,
      final float topRight,
      final float bottomRight,
      final float bottomLeft) {
      return roundedCorners(
              pool,
              inBitmap,
              new DrawRoundedCornerFn() {
                  @Override
                  public void drawRoundedCorners(Canvas canvas, Paint paint, RectFloat rect) {
                      Path path = new Path();
                      path.addRoundRect(
                              rect,
                              new float[] {
                                      topLeft,
                                      topLeft,
                                      topRight,
                                      topRight,
                                      bottomRight,
                                      bottomRight,
                                      bottomLeft,
                                      bottomLeft
                              },
                              Path.Direction.CLOCK_WISE);
                      canvas.drawPath(path, paint);
                  }
              });
  }

    private static PixelMap roundedCorners(
            BitmapPool pool, PixelMap inBitmap, DrawRoundedCornerFn drawRoundedCornerFn) {

        // Alpha is required for this transformation.
        PixelFormat safeConfig = getAlphaSafeConfig(inBitmap);
        PixelMap toTransform = getAlphaSafeBitmap(null, inBitmap);
        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.size = inBitmap.getImageInfo().size;
        initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
        PixelMap result = PixelMap.create(initializationOptions);

        //result.setHasAlpha(true);
        PixelMapShader shader =
                new PixelMapShader(new PixelMapHolder(toTransform), Shader.TileMode.CLAMP_TILEMODE, Shader.TileMode.CLAMP_TILEMODE);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setShader(shader, Paint.ShaderType.PIXELMAP_SHADER);
        RectFloat rect = new RectFloat(0, 0, result.getImageInfo().size.width, result.getImageInfo().size.height);

        BITMAP_DRAWABLE_LOCK.lock();
        try {
            Canvas canvas = new Canvas(new Texture(result));
            canvas.drawColor(Color.TRANSPARENT.getValue(), Canvas.PorterDuffMode.CLEAR);
            drawRoundedCornerFn.drawRoundedCorners(canvas, paint, rect);
            clear(canvas);
        } finally {
            BITMAP_DRAWABLE_LOCK.unlock();
        }

        if (!toTransform.equals(inBitmap)) {
            pool.put(toTransform);
        }

        return result;
    }

  private static final class NoLock implements Lock {

    @Synthetic
    NoLock() {}

    @Override
    public void lock() {
      // do nothing
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
      // do nothing
    }

    @Override
    public boolean tryLock() {
      return true;
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
      return true;
    }

    @Override
    public void unlock() {
      // do nothing
    }

    @NotNull
    @Override
    public Condition newCondition() {
      throw new UnsupportedOperationException("Should not be called");
    }
  }
}
